package com.jd.platform.sample3.service.redis;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jd.platform.sample3.entity.pojo.Produce;
import com.jd.platform.sample3.mapper.ProduceMapper;
import com.jd.platform.sample3.service.ProduceService;
import com.jd.platform.sample3.util.AttributeUtil;
import com.jd.platform.sample3.util.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Service
public class ProduceServiceRedis extends ServiceImpl<ProduceMapper, Produce> {
    @Autowired
    private Cache cache;

    private final String pojo="Produce";

    private final long sleepTime=500; //睡眠时间=更新数据库所需时间=500ms=0.5s


    /**
     * 以下是查询，查询 一个属性值 或 一个对象所有属性的值
     */
    // 根据id查Produce某一属性的值
    public String getValueById(int id,String attribute) throws Exception {
        if (attribute==null) { /* 这里测试默认attribute="name" */
            attribute="name";
        }
        String key=String.format("%s:%s:%s", pojo, attribute, id);
        String value=cache.get(key);
        if(value==null) {
            Produce produce = this.getById(id); //尝试从数据库获取值
            if(produce!=null){ //redisTemplate没有，数据库有
                value = (String) AttributeUtil.getAttributeValue(produce,attribute);
            } else { //该key不存在即没有存值时，视为被恶意访问了多次，为避免缓存穿透，设置值为""
                value="";
            }
            cache.set(key,value);
        }
        System.out.println(value);
        return value;
    }

    // 根据id查Produce所有属性的值
    public Produce getObjectById(int id) throws Exception {
        Produce produce = Produce.builder().id(id).build();


//        //第一种方法
//        List<String> attributes = AttributeUtil.getAttributeNames(Produce.class); //属性名集合
//        for(int i=0;i<attributes.size();i++) {
//            String attribute = attributes.get(i);
//            String value = getValueById(id, attribute); //一次一次的从redis获取大大消耗redis连接
//            AttributeUtil.setAttributeValue(produce, attribute, value);
//        }

        //第二种方法（推荐）
        //先遍历JdHotKeyStore.getValue(key)
        //查不到的数据放一起用redisTemplate.opsForValue().multiGet(keys)
        //还有数据查不到直接统一用this.getById(id)
        //还查不到的话就判定为恶意访问，赋默认值并批量缓存cache.sets(keys,"")
        List<String> keys = getKeysById(id); //key集合
        String[] values=cache.gets(keys); //来自于redis的values
        if(values!=null) {
            for(int i=0;i< keys.size();i++) {
                AttributeUtil.setAttributeValue(produce, keys.get(i), values[i]);
            }
        } else {
            produce = this.getById(id); //尝试从数据库获取值
            if(produce!=null) {
                List<String> values2 = AttributeUtil.getAttributeValues(produce, keys); //属性值集合
                cache.sets(keys,values2);
            } else {
                cache.sets(keys,"");
            }
        }


        System.out.println(produce);
        return produce;
    }

    /**
     * TODO: 条件查询
     * View → Tool Windows → 该字符串，会显示项目中所有包含 该字符串 的注释，用于标记未完善的地方方便后续寻找并完善
     *
     * 条件查询真封装到redis太复杂了，mysql还有索引帮助查询，所以没必要，这里只介绍相对容易实现的简单条件查询
     * 一、根据已知值num获取price大于（或小于等于）num的Produce
     * 二、根据已知属性值查Produce剩余未知属性的值（可能是0-n个Produce）
     */


    /**
     * 以下是更新数据库（添加、修改、删除），redis需与数据库保持一致，以下是解决方案
     * 一、延迟双删    （实现简单，把握好延迟时间即可）（本项目使用该方案）
     * 二、监听binlog删除+mq队列异步重试
     */
    //单独添加或更新
    public void InsertOrUpdate(Produce produce) throws InterruptedException {
        List<String> keys = getKeysById(produce); //key集合
        //属性值集合（这里不需要，因为get如果redis获取不到则从数据库获取并存储到redis中）
//        List<String> values = AttributeUtil.getAttributeValues(produce, keys);

        cache.removes(keys);
        this.saveOrUpdate(produce);
        Thread.sleep(sleepTime);
        cache.removes(keys);
    }

    //批量添加或更新
    public void InsertsOrUpdates(List<Produce> produces) throws InterruptedException {
        List<String> keys = getKeysById(produces); //key集合
        //属性值集合（这里不需要，因为get如果redis获取不到则从数据库获取并存储到redis中）
//        List<String> values = AttributeUtil.getAttributeValues(produce, keys);

        cache.removes(keys);
        this.saveOrUpdateBatch(produces);
        Thread.sleep(sleepTime);
        cache.removes(keys);
    }

    //单独删除
    public void delete(int id) throws InterruptedException {
        List<String> keys = getKeysById(id); //key集合
        //属性值集合（这里不需要，因为get如果redis获取不到则从数据库获取并存储到redis中）
//        List<String> values = AttributeUtil.getAttributeValues(produce, keys);

        cache.removes(keys);
        this.removeById(id);
        Thread.sleep(sleepTime);
        cache.removes(keys);
    }

    //批量删除
    public void deletes(List<Integer> ids) throws InterruptedException {
        List<String> keys = getKeysById(ids); //key集合
        //属性值集合（这里不需要，因为get如果redis获取不到则从数据库获取并存储到redis中）
//        List<String> values = AttributeUtil.getAttributeValues(produce, keys);

        cache.removes(keys);
        this.removeByIds(ids);
        Thread.sleep(sleepTime);
        cache.removes(keys);
    }


    /**
     * 以下是获取key名的集合
     */
    public List<String> getKeysById(Object object) {
        List<String> attributes = AttributeUtil.getAttributeNames(Produce.class); //属性名集合
        List<String> keys = new ArrayList<>();
        int id;
        if(object instanceof List) {
            keys = getKeysByIds((List) object);
        } else {
            if(object instanceof Integer) {
                id = (Integer) object;
//        } else if(object instanceof Produce) {
            } else {
                id = ((Produce) object).getId();
            }
            for(int i=0;i<attributes.size();i++) {
                String attribute = attributes.get(i);
                keys.add(String.format("%s:%s:%s", pojo, attribute, id)); //尾部加入
            }
        }

        return keys;
    }

    public List<String> getKeysByIds(List<Object> objects) {
        List<String> keys = new ArrayList<>();
        if(objects.get(0) instanceof Integer) {
            for(int i=0;i<objects.size();i++) {
                Object object = objects.get(i);
                int id = (int) object;
                List<String> pre = getKeysById(id);
                keys.addAll(pre);
            }
//        } else if(objects.get(0) instanceof Produce) {
        } else {
            for(int i=0;i<objects.size();i++) {
                Object object = objects.get(i);
                Produce produce = (Produce) object;
                List<String> pre = getKeysById(produce);
                keys.addAll(pre);
            }
        }
        return keys;
    }


    /**
     * 以下方法是以上方法的组合
     */
    public Object getById_Redis(int id,String... attributes) throws Exception {
        if (attributes == null || attributes.length == 0) {
            return getObjectById(id); //返回Produce
        } else {
            return getValueById(id,attributes[0]); //返回属性名为attributes[0]的属性值
        }
    }

    public void InsertOrUpdate_Redis(Object object) throws Exception {
        if(object instanceof Produce){
            Produce produce = (Produce) object;
            InsertOrUpdate(produce);
        } else {
            List<Produce> produces = (List<Produce>) object;
            InsertsOrUpdates(produces);
        }
    }

    public void deleteById_Redis(Object object) throws Exception {
        if(object instanceof Integer){
            int id = (Integer) object;
            delete(id);
        } else {
            List<Integer> ids = (List<Integer>) object;
            deletes(ids);
        }
    }
}
